﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

namespace TwainLib
{
    public class DeviceSourceManager
    {
        public DeviceSourceManager(IntPtr hWnd, short majorVersion = 1, short minorVersion = 0, short languageCode = 13,
            short countryCode = 1, string appInfo = "Unknown", short protocolMajor = TwProtocol.Major, 
            short protocolMinor = TwProtocol.Minor, int supportedGroups = (int)(TwDG.Image | TwDG.Control) | (int)(TwDF.APP2),
            string manufacturer = "Unknown", string productFamily = "Unknown", string productName = "Unknown")
        {
            hWndParent = hWnd;

            entryPoint = new TwEntryPointMethods();
            entryPoint.TwEntryPoint = new TwEntryPointDelegate(NativeLibMethods.DSMEntryPoint);
            entryPoint.TwMemAllocate = new TwMemAllocateDelegate(Marshal.AllocHGlobal);
            entryPoint.TwMemFree = new TwMemFreeDelegate(Marshal.FreeHGlobal);
            entryPoint.TwMemLock = new TwMemLockDelegate(NativeLibMethods.GlobalLock);
            entryPoint.TwMemUnlock = new TwMemUnlockDelegate(NativeLibMethods.GlobalUnlock);

            id = new TwIdentity();

            id.Id = IntPtr.Zero;
            id.Version.MajorNum = majorVersion;
            id.Version.MinorNum = minorVersion;
            id.Version.Language = languageCode;
            id.Version.Country = countryCode;
            id.Version.Info = appInfo;
            id.ProtocolMajor = protocolMajor;
            id.ProtocolMinor = protocolMinor;
            id.SupportedGroups = supportedGroups;
            id.Manufacturer = manufacturer;
            id.ProductFamily = productFamily;
            id.ProductName = productName;
        }

        #region DSM方法
        
        // DG_CONTROL / DAT_PARENT / MSG_OPENDSM
        public bool Open()
        {
            TwRC rc = entryPoint.TwEntryPointT(id, null, TwDG.Control, TwDAT.Parent, TwMSG.OpenDSM, ref hWndParent);
            if (rc == TwRC.Failure)
            {
                HandleErrorCode();
                return false;
            }

            // Test DF_DSM2
            if ((id.SupportedGroups & (Int32)TwDF.DSM2) != 0)
            {
                GetEntryPoint();
            }

            return true;
        }

        // DG_CONTROL / DAT_PARENT / MSG_CLOSEDSM
        public void Close()
        {
            TwRC rc = entryPoint.TwEntryPoint(id, null, TwDG.Control, TwDAT.Parent, TwMSG.CloseDSM, hWndParent);
            if (rc == TwRC.Failure)
            {
                HandleErrorCode();
            }
        }

        // DG_CONTROL / DAT_ENTRYPOINT / MSG_GET
        internal TwRC GetEntryPoint()
        {
            Debug.Assert(id != null);

            TwRC rc = entryPoint.TwEntryPointT(id, null, TwDG.Control, TwDAT.EntryPoint, TwMSG.Get, ref entryPoint);
            if (rc == TwRC.Failure)
            {
                HandleErrorCode();
            }

            return rc;
        }

        // DG_CONTROL / DAT_STATUS / MSG_GET
        internal TwCC GetErrorCode(TwIdentity dsId = null)
        {
            TwStatus status = new TwStatus();
            TwRC rc = entryPoint.TwEntryPointT<TwStatus>(id, dsId, TwDG.Control, TwDAT.Status, TwMSG.Get, ref status);
            if (rc == TwRC.Failure)
            {
                throw new NoSuchDeviceSourceException();
            }
            return status.ConditionCode;
        }

        // DG_CONTROL / DAT_IDENTITY / MSG_GETDEFAULT
        public DeviceSource GetDefaultDeviceSource()
        {
            DeviceSource ds = null;

            TwIdentity dsId = new TwIdentity();
            dsId.Id = IntPtr.Zero;

            TwRC rc = entryPoint.TwEntryPointT(id, null, TwDG.Control, TwDAT.Identity, TwMSG.GetDefault, ref dsId);
            if (rc == TwRC.Failure)
            {
                HandleErrorCode();
            }
            ds = new DeviceSource(this, dsId);

            return ds;
        }

        // DG_CONTROL / DAT_IDENTITY / MSG_GETFIRST
        public DeviceSource GetFirstDeviceSource()
        {
            DeviceSource ds = null;

            TwIdentity dsId = new TwIdentity();
            dsId.Id = IntPtr.Zero;

            TwRC rc = entryPoint.TwEntryPointT(id, null, TwDG.Control, TwDAT.Identity, TwMSG.GetFirst, ref dsId);
            if (rc == TwRC.Failure)
            {
                HandleErrorCode();
            }
            ds = new DeviceSource(this, dsId);
            return ds;
        }

        // DG_CONTROL / DAT_IDENTITY / MSG_GETNEXT
        public DeviceSource GetNextDeviceSource()
        {
            DeviceSource ds = null;

            TwIdentity dsId = new TwIdentity();
            dsId.Id = IntPtr.Zero;

            TwRC rc = entryPoint.TwEntryPointT(id, null, TwDG.Control, TwDAT.Identity, TwMSG.GetNext, ref dsId);
            if (rc == TwRC.Failure)
            {
                HandleErrorCode();
                return null;
            }
            else if (rc == TwRC.EndOfList)
            {
                return null;
            }
            ds = new DeviceSource(this, dsId);
            return ds;
        }

        // DG_CONTROL / DAT_IDENTITY / MSG_USERSELECT
        public DeviceSource GetUserSelectedDeviceSource()
        {
            TwIdentity dsId = new TwIdentity();
            dsId.Id = IntPtr.Zero;

            TwRC rc = entryPoint.TwEntryPointT(id, null, TwDG.Control, TwDAT.Identity, TwMSG.UserSelect, ref dsId);
            if (rc == TwRC.Failure)
            {
                HandleErrorCode();
                return null;
            }
            else
            {
                return new DeviceSource(this, dsId);
            }
        }

        // DG_CONTROL / DAT_IDENTITY / MSG_OPENDS
        public bool OpenDS(DeviceSource ds)
        {
            TwIdentity dsId = ds.Id;

            TwRC rc = entryPoint.TwEntryPointT(id, null, TwDG.Control, TwDAT.Identity, TwMSG.OpenDS, ref dsId);
            if (rc == TwRC.Failure)
            {
                HandleErrorCode();
                return false;
            }

            return true;
        }

        // DG_CONTROL / DAT_IDENTITY / MSG_CLOSEDS
        public void CloseDS(DeviceSource ds)
        {
            TwIdentity dsId = ds.Id;

            TwRC rc = entryPoint.TwEntryPointT(id, null, TwDG.Control, TwDAT.Identity, TwMSG.CloseDS, ref dsId);
            if (rc == TwRC.Failure)
            {
                HandleErrorCode();
            }
        }

        // DG_CONTROL / DAT_IDENTITY / MSG_SET

        // DG_CONTROL / DAT_CAPABILITY / MSG_GET
        internal void GetCapability(DeviceSourceCapability cap)
        {
            Debug.Assert(cap != null);

            DeviceSourceCapability.TwCapability twCap = cap.Capability;

            TwRC rc = entryPoint.TwEntryPointT(id, cap.DeviceSource.Id, TwDG.Control, TwDAT.Capability, TwMSG.Get, ref twCap);

            if (rc == TwRC.Failure)
            {
                cap.CanSupported = false;
                
                HandleErrorCode();
            }
            else
            {
                cap.CanSupported = true;
                cap.FromHandle();
            }
        }
        
        // DG_CONTROL / DAT_CAPABILITY / MSG_GETHELP
        internal void GetCapabilityHelp(DeviceSourceCapability cap)
        {
            Debug.Assert(cap != null);

            DeviceSourceCapability.TwCapability twCap = cap.Capability;

            TwRC rc = entryPoint.TwEntryPointT(id, cap.DeviceSource.Id, TwDG.Control, TwDAT.Capability, TwMSG.GetHelp, ref twCap);

            if (rc == TwRC.Failure)
            {
                cap.CanSupported = false;
                HandleErrorCode();
            }
            else
            {
                cap.CanSupported = true;
                cap.GenerateHelpText();
            }
        }

        // DG_CONTROL / DAT_CAPABILITY / MSG_GETLABEL
        internal void GetCapabilityLabel(DeviceSourceCapability cap)
        {
            Debug.Assert(cap != null);

            DeviceSourceCapability.TwCapability twCap = cap.Capability;

            TwRC rc = entryPoint.TwEntryPointT(id, cap.DeviceSource.Id, TwDG.Control, TwDAT.Capability, TwMSG.GetLabel, ref twCap);

            if (rc == TwRC.Failure)
            {
                cap.CanSupported = false;
                HandleErrorCode();
            }
            else
            {
                cap.CanSupported = true;
                cap.GenerateLabelText();
            }
        }

        // DG_CONTROL / DAT_CAPABILITY / MSG_SET
        internal void SetCapability(DeviceSourceCapability cap)
        {
            Debug.Assert(cap != null);

            DeviceSourceCapability.TwCapability twCap = cap.Capability;

            TwRC rc = entryPoint.TwEntryPointT(id, cap.DeviceSource.Id, TwDG.Control, TwDAT.Capability, TwMSG.Set, ref twCap);

            if (rc == TwRC.Failure)
            {
                HandleErrorCode();
            }

            entryPoint.TwMemFree(cap.Capability.Handle);
            cap.Capability.Handle = IntPtr.Zero;
        }

        #endregion

        public List<DeviceSource> GetAllDeviceSources()
        {
            List<DeviceSource> dss = null;

            DeviceSource ds = GetFirstDeviceSource();
            if (ds != null)
                dss = new List<DeviceSource>();

            while (ds != null)
            {
                dss.Add(ds);
                ds = GetNextDeviceSource();
            }

            return dss;
        }

        private void HandleErrorCode()
        {
            TwCC errorCode = GetErrorCode();
            switch (errorCode)
            {
                case TwCC.LowMemory:
                    throw new LowMemoryException();
                case TwCC.SeqError:
                    throw new InvalidCastException();
                case TwCC.BadProtocol:
                    throw new BadProtocolException();
                case TwCC.NoDS:
                    throw new NoDeviceSourceException();
                case TwCC.MaxConnections:
                    throw new MaxConnectionsException();
                case TwCC.OperationError:
                    throw new OperationErrorException();
            }
        }

        internal TwEntryPointMethods EntryPoint
        {
            get
            {
                return entryPoint;
            }
        }

        internal TwIdentity Id
        {
            get
            {
                return id;
            }
        }

        private TwEntryPointMethods entryPoint;
        private TwIdentity id;
        private IntPtr hWndParent;

        public IntPtr HWndParent
        {
            get { return hWndParent; }
            set { hWndParent = value; }
        }
    }
}
